/*
 * Copyright © 2009-10 Sam Thursfield
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the licence, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: Sam Thursfield <ssssam@gmail.com>
 */

/* GRegistryBackend implementation notes:
 *
 *   - All settings are stored under the path:
 *       HKEY_CURRENT_USER\Software\GSettings\
 *     This means all settings are per-user. Permissions and system-wide
 *     defaults are not implemented and will probably always be out of scope of
 *     the Windows port of GLib.
 *
 *   - The registry type system is limited. Most GVariant types are stored as
 *     literals via g_variant_print/parse(). Strings are stored without the
 *     quotes that GVariant requires. Integer types are stored as native
 *     REG_DWORD or REG_QWORD. The REG_MULTI_SZ (string array) type could be
 *     used to avoid flattening container types.
 *
 *   - Notifications are handled; the change event is watched for in a separate
 *     thread (Windows does not provide a callback API) which sends them with
 *     g_idle_add to the GLib main loop. The threading is done using Windows
 *     API functions, so there is no dependence on GThread.
 *
 *   - Windows doesn't tell us which value has changed. This means we have to
 *     maintain a cache of every stored value so we can play spot the
 *     difference. This should not be a performance issue because if you are
 *     storing thousands of values in GSettings, you are probably using it
 *     wrong.
 *
 *   - The cache stores the value as a registry type. Because many variants are
 *     stored as string representations, values which have changed equality but
 *     not equivalence may trigger spurious change notifications. GSettings
 *     users must already deal with this possibility and converting all data to
 *     GVariant values would be more effort.
 *
 *   - Because we have to cache every registry value locally, reads are done
 *     from the cache rather than directly from the registry. Writes update
 *     both. This means that the backend will not work if the watch thread is
 *     not running. A GSettings object always subscribes to changes so we can
 *     be sure that the watch thread will be running, but if for some reason
 *     the backend is being used directly you should bear that in mind.
 *
 *   - The registry is totally user-editable, so we are very forgiving about
 *     errors in the data we get.
 *
 *   - The registry uses backslashes as path separators. GSettings keys only
 *     allow [A-Za-z\-] so no escaping is needed. No attempt is made to solve
 *     clashes between keys differing only in case.
 *
 *   - RegCreateKeyA is used - Windows can also handle UTF16LE strings.
 *     GSettings doesn't pay any attention to encoding, so by using ANSI we
 *     hopefully avoid passing any invalid Unicode.
 *
 *   - The Windows registry has the following limitations: a key may not exceed
 *     255 characters, an entry's value may not exceed 16,383 characters, and
 *     all the values of a key may not exceed 65,535 characters.
 *
 *   - Terminology:
 *     * in GSettings, a 'key' is eg. /desktop/gnome/background/primary-color
 *     * in the registry, the 'key' is path, which contains some 'values'.
 *     * in this file, any GSettings key is a 'key', while a registry key is
 *       termed a 'path', which contains 'values'.
 *
 *   - My set of tests for this backend are currently at:
 *       http://gitorious.org/gsettings-gtk/gsettings-test.git
 *
 *   - There is an undocumented function in ntdll.dll which might be more
 *     than RegNotifyChangeKeyValue(), NtNotifyChangeKey:
 *       http://source.winehq.org/source/dlls/ntdll/reg.c#L618
 *       http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Key/NtNotifyChangeKey.html
 *
 *   - If updating the cache ever becomes a performance issue it may make sense
 *     to use a red-black tree, but I don't currently think it's worth the time
 */

#include "config.h"

#include "gregistrysettingsbackend.h"
#include "gsimplepermission.h"
#include "gsettingsbackend.h"
#include "giomodule.h"


#define _WIN32_WINNT 0x0500
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

//#define TRACE

/* GSettings' limit */
#define MAX_KEY_NAME_LENGTH   32

/* Testing (on Windows XP SP3) shows that WaitForMultipleObjects fails with
 * "The parameter is incorrect" after 64 watches. We need one for the
 * message_sent cond, which is allowed for in the way the watches_remaining
 * variable is used.
 */
#define MAX_WATCHES   64

/* A watch on one registry path and its subkeys */
typedef struct
{
  HANDLE event;
  HKEY   hpath;
  char  *prefix;
  GNode *cache_node;
} RegistryWatch;


/* Simple message passing for the watch thread. Not enough traffic to
 * justify a queue.
 */
typedef enum
{
  WATCH_THREAD_NONE,
  WATCH_THREAD_ADD_WATCH,
  WATCH_THREAD_REMOVE_WATCH,
  WATCH_THREAD_STOP
} WatchThreadMessageType;

typedef struct
{
  WatchThreadMessageType type;
  RegistryWatch watch;
} WatchThreadMessage;


typedef struct
{
  GSettingsBackend *owner;
  HANDLE           *thread;

  /* Details of the things we are watching. */
  int watches_remaining;
  GPtrArray *events, *handles, *prefixes, *cache_nodes;

  /* Communication with the main thread. Only one message is stored at a time,
   * to make sure that messages are acknowledged before being overwritten we
   * create two events - one is signalled when a new message is set, the
   * other is signalled by the thread when it has processed the message.
   */
  WatchThreadMessage message;
  CRITICAL_SECTION *message_lock;
  HANDLE message_sent_event, message_received_event;
} WatchThreadState;


#define G_TYPE_REGISTRY_BACKEND      (g_registry_backend_get_type ())
#define G_REGISTRY_BACKEND(inst)     (G_TYPE_CHECK_INSTANCE_CAST ((inst),         \
                                      G_TYPE_REGISTRY_BACKEND, GRegistryBackend))
#define G_IS_REGISTRY_BACKEND(inst)  (G_TYPE_CHECK_INSTANCE_TYPE ((inst),         \
                                      G_TYPE_REGISTRY_BACKEND))


typedef GSettingsBackendClass GRegistryBackendClass;

typedef struct {
  GSettingsBackend  parent_instance;

  char             *base_path;

  /* A stored copy of the whole tree being watched. When we receive a change notification
   * we have to check against this to see what has changed ... every time ...*/
  CRITICAL_SECTION *cache_lock;
  GNode            *cache_root;

  WatchThreadState *watch;
} GRegistryBackend;

G_DEFINE_TYPE_WITH_CODE (GRegistryBackend,
                         g_registry_backend,
                         G_TYPE_SETTINGS_BACKEND,
                         g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
                                                         g_define_type_id, "registry", 90))


/**********************************************************************************
 * Utility functions
 **********************************************************************************/

#include <stdio.h>
static void
trace (const char *format, ...)
{
  #ifdef TRACE
  va_list va; va_start (va, format);
  vprintf (format, va); fflush (stdout);
  va_end (va);
  #endif
};

/* g_message including a windows error message. It is not useful to have an
 * equivalent function for g_warning because none of the registry errors can
 * result from programmer error (Microsoft programmers don't count), instead
 * they will mostly occur from people messing with the registry by hand. */
static void
g_message_win32_error (DWORD result_code,
                       const gchar *format,
                      ...)
{
  va_list va;
  gint pos;
  gchar win32_message[1024];

  if (result_code == 0)
    result_code = GetLastError ();

  va_start (va, format);
  pos = g_vsnprintf (win32_message, 512, format, va);

  win32_message[pos++] = ':'; win32_message[pos++] = ' ';

  FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, result_code, 0, (LPTSTR)(win32_message+pos),
                1023 - pos, NULL);

  if (result_code == ERROR_KEY_DELETED)
    trace ("(%s)", win32_message);
  else
    g_message (win32_message);
};


/* Make gsettings key into a registry path & value pair. 
 * 
 * Note that the return value *only* needs freeing - registry_value_name
 * is a pointer to further inside the same block of memory.
 */
static gchar *
parse_key (const gchar  *key_name,
           const gchar  *registry_prefix,
           gchar       **value_name)
{
  gchar *path_name, *c;

  /* All key paths are treated as absolute; gsettings doesn't seem to enforce a
   * preceeding /.
   */
  if (key_name[0] == '/')
    key_name ++;

  if (registry_prefix == NULL)
      path_name = g_strdup (key_name);
  else
      path_name = g_strjoin ("/", registry_prefix, key_name, NULL);

  /* Prefix is expected to be in registry format (\ separators) so don't escape that. */
  for (c=path_name+(registry_prefix?strlen(registry_prefix):0); *c!=0; c++)
      if (*c == '/')
        {
          *c = '\\';
          (*value_name) = c;
        }

  **value_name = 0; (*value_name)++;
  return path_name;
};


static DWORD
g_variant_get_as_dword (GVariant *variant)
{
  switch (g_variant_get_type_string (variant)[0])
    {
      case 'b': return g_variant_get_boolean (variant);
      case 'y': return g_variant_get_byte (variant);
      case 'n': return g_variant_get_int16 (variant);
      case 'q': return g_variant_get_uint16 (variant);
      case 'i': return g_variant_get_int32 (variant);
      case 'u': return g_variant_get_uint32 (variant);
      default:  g_warn_if_reached ();
    }
  return 0;
}

static DWORDLONG
g_variant_get_as_qword (GVariant *variant)
{
  switch (g_variant_get_type_string (variant)[0])
    {
      case 'x': return g_variant_get_int64 (variant);
      case 't': return g_variant_get_uint64 (variant);
      default:  g_warn_if_reached ();
    }
  return 0;
}


static void
handle_read_error (LONG         result,
                   const gchar *path_name,
                   const gchar *value_name)
{
  /* file not found means key value not set, this isn't an error for us. */
  if (result != ERROR_FILE_NOT_FOUND)
      g_message_win32_error (result, "Unable to query value %s/%s: %s.\n",
                             path_name, value_name);
}

/***************************************************************************
 * Cache of registry values
 ***************************************************************************/

/* Generic container for registry values */
typedef struct {
  DWORD type;

  union {
    gint  dword;  /* FIXME: could inline QWORD on 64-bit systems too */
    void *ptr;
  };
} RegistryValue;

static char *
registry_value_dump (RegistryValue value)
{
  if (value.type == REG_DWORD)
    return g_strdup_printf ("%i", value.dword);
  else if (value.type == REG_QWORD)
    return g_strdup_printf ("%I64i", value.ptr==NULL? 0: *(DWORDLONG *)value.ptr);
  else if (value.type == REG_SZ)
    return g_strdup_printf ("%s", (char *)value.ptr);
  else if (value.type == REG_NONE)
    return g_strdup_printf ("<empty>");
  else
    return g_strdup_printf ("<invalid>");
}

static void
registry_value_free (RegistryValue value)
{
  if (value.type == REG_SZ || value.type == REG_QWORD)
    g_free (value.ptr);
  value.type = REG_NONE;
  value.ptr = NULL;
}


/* The registry cache is stored as a tree, for easy traversal. Right now we
 * don't sort it in a clever way. Each node corresponds to a path element
 * ('key' in registry terms) or a value.
 *
 * Each subscription uses the same cache. Because GSettings can subscribe to
 * the tree at any node any number of times, we need to reference count the
 * nodes.
 */
typedef struct
{
  /* Component of path that this node represents */
  gchar *name;           

  /* If a watch is subscribed at this point (subscription_count > 0) we can
   * block its next notification. This is useful because if two watches cover
   * the same path, both will trigger when it changes. It also allows changes
   * done by the application to be ignored by the watch thread.
   */
  gint32 block_count        : 8;

  /* Number of times g_settings_subscribe has been called for this location
   * (I guess you can't subscribe more than 16383 times) */
  gint32 subscription_count : 14;
  
  gint32 ref_count          : 9;

  gint32 touched            : 1;
  RegistryValue value;
} RegistryCacheItem;



static GNode *
registry_cache_add_item (GNode        *parent,
                         gchar        *name,
                         RegistryValue value,
                         gint          ref_count)
{
  RegistryCacheItem *item = g_slice_new (RegistryCacheItem);
  GNode *cache_node;

  g_return_val_if_fail (name != NULL, NULL);
  g_return_val_if_fail (parent != NULL, NULL);

  /* Ref count should be the number of watch points above this node */
  item->ref_count = ref_count;

  item->name = g_strdup (name);
  item->value = value;
  item->subscription_count = 0;
  item->block_count = 0;
  item->touched = FALSE;
  trace ("\treg cache: adding %s to %s\n", name, ((RegistryCacheItem *)parent->data)->name);

  cache_node = g_node_new (item);
  g_node_append (parent, cache_node);
  return cache_node;
}

/* The reference counting of cache tree nodes works like this: when a node is
 * subscribed to (GSettings wants us to watch that path and everything below
 * it) the reference count of that node and everything below is increased, as
 * well as each parent up to the root.
 */


static void
_ref_down (GNode *node)
{
  RegistryCacheItem *item = node->data;
  g_node_children_foreach (node, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)_ref_down, NULL);
  item->ref_count ++;
}
static void
registry_cache_ref_tree (GNode *tree)
{
  RegistryCacheItem *item = tree->data;
  GNode *node = tree->parent;

  g_return_if_fail (tree != NULL);

  item->ref_count ++;

  g_node_children_foreach (tree, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)_ref_down, NULL);

  for (node=tree->parent; node; node=node->parent)
    {
      item = node->data;
      item->ref_count ++;
    }
}

static void
_free_cache_item (RegistryCacheItem *item)
{
  trace ("\t -- Free node %s\n", item->name);
  g_free (item->name);
  registry_value_free (item->value);
  g_slice_free (RegistryCacheItem, item);
}

/* Unreferencing has to be done bottom-up */
static void
_unref_node (GNode *node)
{
  RegistryCacheItem *item = node->data;

  item->ref_count --;

  g_warn_if_fail (item->ref_count >= 0);

  if (item->ref_count == 0)
    {
      _free_cache_item (item);
      g_node_destroy (node);
    }
}

static void
_unref_down (GNode *node)
{
  g_node_children_foreach (node, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)_unref_down, NULL);
  _unref_node (node);
}

static void
registry_cache_unref_tree (GNode *tree)
{
  GNode *parent = tree->parent, *next_parent;

  _unref_down (tree);

  while (parent)
    {
      next_parent = parent->parent;
      _unref_node (parent);
      parent = next_parent;
    }
}


static void
registry_cache_dump (GNode    *cache_node,
                     gpointer  data)
{
  RegistryCacheItem *item = cache_node->data;

  int depth     = GPOINTER_TO_INT(data),
      new_depth = depth+1,
      i;

  g_return_if_fail (cache_node != NULL);

  for (i=0; i<depth; i++)
    g_print ("  ");
  if (item == NULL)
    g_print ("*root*\n");
  else
    g_print ("'%s'  [%i] @ %x = %s\n", item->name, item->ref_count, (guint)cache_node,
             registry_value_dump (item->value));
  g_node_children_foreach (cache_node, G_TRAVERSE_ALL, registry_cache_dump,
                           GINT_TO_POINTER (new_depth));
}


typedef struct
{
  gchar *name;
  GNode *result;
} RegistryCacheSearch;

static gboolean
registry_cache_find_compare (GNode    *node,
                             gpointer  data)
{
  RegistryCacheSearch *search = data;
  RegistryCacheItem *item = node->data;

  if (item == NULL)  /* root node */
    return FALSE;

  g_return_val_if_fail (search->name != NULL, FALSE);
  g_return_val_if_fail (item->name != NULL, FALSE);

  if (strcmp (search->name, item->name) == 0)
    {
      search->result = node;
      return TRUE;
    }
  return FALSE;
}

static GNode *
registry_cache_find_immediate_child (GNode *node,
                                     gchar *name)
{
  RegistryCacheSearch search;
  search.result = NULL;
  search.name = name;
  g_node_traverse (node, G_POST_ORDER, G_TRAVERSE_ALL, 2,
                   registry_cache_find_compare, &search);
  return search.result;  
}


static GNode *
registry_cache_get_node_for_key_recursive (GNode    *node,
                                           gchar    *key_name,
                                           gboolean  create_if_not_found,
                                           gint      n_parent_watches)
{
  RegistryCacheItem *item;
  gchar *component = key_name,
        *c         = strchr (component, '/');

  if (c != NULL)
    *c = 0;

  /* We count up how many watch points we travel through finding this node,
   * because a new node should have as many references as there are watches at
   * points above it in the tree.
   */
  item = node->data;
  if (item->subscription_count > 0)
    n_parent_watches ++;  

  GNode *child = registry_cache_find_immediate_child (node, component);
  if (child == NULL && create_if_not_found)
    {
      item = g_slice_new (RegistryCacheItem);
      item->name = g_strdup (component);
      item->value.type = REG_NONE;
      item->value.ptr = NULL;
      item->ref_count = n_parent_watches;
      child = g_node_new (item);
      g_node_append (node, child);
      trace ("\tget node for key recursive: new %x = %s.\n", node, item->name);
    }

  /* We are done if there are no more path components. Allow for a trailing /. */
  if (child==NULL || c == NULL || *(c+1)==0)
    return child;
  else
    {
      trace ("get node for key recursive: next: %s.\n", c+1);
      return registry_cache_get_node_for_key_recursive
               (child, c+1, create_if_not_found, n_parent_watches);
    }
}

/* Look up a GSettings key in the cache. */
static GNode *
registry_cache_get_node_for_key (GNode       *root,
                                 const gchar *key_name,
                                 gboolean     create_if_not_found)
{
  GNode *child = NULL,
        *result = NULL;
  gchar *component, *c;

  g_return_val_if_fail (key_name != NULL, NULL);

  if (key_name[0] == '/')
    key_name ++;

  /* Ignore preceeding / */
  component = g_strdup (key_name);
  c = strchr (component, '/');
  if (c != NULL)
    *c = 0;

  child = registry_cache_find_immediate_child (root, component);
  if (child == NULL && create_if_not_found)
    {
      /* Reference count is set to 0, tree should be referenced by the caller */
      RegistryCacheItem *item = g_slice_new (RegistryCacheItem);
      item->value.type = REG_NONE;
      item->value.ptr = NULL;
      item->name = g_strdup (component);
      item->ref_count = 0;
      trace ("get_node_for_key: New node for component '%s'\n", item->name);
      child = g_node_new (item);
      g_node_append (root, child);
    }

  if (c == NULL)
    result = root;
  else if (*(c+1)==0)
    result = child;
  else if (child != NULL)
    result = registry_cache_get_node_for_key_recursive (child, c+1, create_if_not_found, 0);

  g_free (component);

  return result;
}

/* Check the cache node against the registry key it represents. Return TRUE if
 * they differ, and update the cache with the new value.
 */
static gboolean
registry_cache_update_node (GNode        *cache_node,
                            RegistryValue registry_value)
{
  RegistryCacheItem *cache_item = cache_node->data;

  g_return_val_if_fail (cache_node != NULL, FALSE);
  g_return_val_if_fail (cache_item != NULL, FALSE);

  if (registry_value.type != cache_item->value.type)
    {
      /* The type has changed. Update cache item and register it as changed.
       * Either the schema has changed and this is entirely legitimate, or
       * whenever the app reads the key it will get the default value due to
       * the type mismatch.
       */
      cache_item->value = registry_value;
      return TRUE;
    }
 
  switch (registry_value.type)
    {
      case REG_DWORD:
        {
          if (cache_item->value.dword == registry_value.dword)
            return FALSE;
          else
            {
              cache_item->value.dword = registry_value.dword;
              return TRUE;
            }
        }
      case REG_QWORD:
        {
          g_return_val_if_fail (registry_value.ptr != NULL &&
                                cache_item->value.ptr != NULL, FALSE);

          if (memcmp (registry_value.ptr, cache_item->value.ptr, 8)==0)
            {
              g_free (registry_value.ptr);
              return FALSE;
            }
          else
            {
              g_free (cache_item->value.ptr);
              cache_item->value.ptr = registry_value.ptr;
              return TRUE;
            }
        }
      case REG_SZ:
        {
          /* Value should not exist if it is NULL, an empty string is "" */
          g_return_val_if_fail (cache_item->value.ptr != NULL, FALSE);
          g_return_val_if_fail (registry_value.ptr != NULL, FALSE);

          if (strcmp (registry_value.ptr, cache_item->value.ptr) == 0)
            {
              g_free (registry_value.ptr);
              return FALSE;
            }
          else
            {
              g_free (cache_item->value.ptr);
              cache_item->value.ptr = registry_value.ptr;
              return TRUE;
            }
        }
      default:
        g_warning ("gregistrybackend: registry_cache_update_node: Unhandled value type :(");
        return FALSE;
    }
}

/* Blocking notifications is a useful optimisation. When a change is made
 * through GSettings we update the cache manually, but a notifcation is
 * triggered as well. This function is also used for nested notifications,
 * eg. if /test and /test/foo are watched, and /test/foo/value is changed then
 * we will get notified both for /test/foo and /test and it is helpful to block
 * the second.
 */
static void
registry_cache_block_notification (GNode *node)
{
  RegistryCacheItem *item = node->data;

  g_return_if_fail (node != NULL);

  if (item->subscription_count > 0)
    item->block_count ++;

  if (node->parent != NULL)
    registry_cache_block_notification (node->parent);
}

static void
registry_cache_destroy_tree (GNode            *node,
                             WatchThreadState *self);

/***************************************************************************
 * Reading and writing
 ***************************************************************************/

static gboolean
registry_read (HKEY           hpath,
               const gchar   *path_name,
               const gchar   *value_name,
               RegistryValue *p_value)
{
  LONG      result;
  DWORD     value_data_size;
  gpointer *buffer;

  g_return_val_if_fail (p_value != NULL, FALSE);

  p_value->type = REG_NONE;
  p_value->ptr = NULL;

  result = RegQueryValueExA (hpath, value_name, 0, &p_value->type, NULL, &value_data_size);
  if (result != ERROR_SUCCESS)
     {
      handle_read_error (result, path_name, value_name);
      return FALSE;
     }

  if (p_value->type == REG_SZ && value_data_size == 0)
    {
      p_value->ptr = g_strdup ("");
      return TRUE;
    }

  if (p_value->type == REG_DWORD)
    /* REG_DWORD is inlined */
    buffer = (void *)&p_value->dword;
  else
    buffer = p_value->ptr = g_malloc (value_data_size);

  result = RegQueryValueExA (hpath, value_name, 0, NULL, (LPBYTE)buffer, &value_data_size);
  if (result != ERROR_SUCCESS)
    {
      handle_read_error (result, path_name, value_name);
      return FALSE;
    }

  return TRUE;
}


static GVariant *
g_registry_backend_read (GSettingsBackend   *backend,
                         const gchar        *key_name,
                         const GVariantType *expected_type,
                         gboolean            default_value)
{
  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);

  GNode         *cache_node;
  RegistryValue  registry_value;
  GVariant      *gsettings_value = NULL;
  gchar         *gsettings_type;

  g_return_val_if_fail (expected_type != NULL, NULL);

  if (default_value)
    return NULL;

  /* Simply read from the cache, which is updated from the registry by the
   * watch thread as soon as changes can propagate. Any changes not yet in the
   * cache will have the 'changed' signal emitted after this function returns.
   */
  EnterCriticalSection (self->cache_lock);
  cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE);
  LeaveCriticalSection (self->cache_lock);

  trace ("Reading key %s, cache node %x\n", key_name, cache_node);

  /* Maybe it's not set, we can return to default */
  if (cache_node == NULL)
    return NULL;

  trace ("\t- cached value %s\n", registry_value_dump (((RegistryCacheItem *)cache_node->data)->value));

  registry_value = ((RegistryCacheItem *)cache_node->data)->value;

  gsettings_type = g_variant_type_dup_string (expected_type);

  /* The registry is user-editable, so we need to be fault-tolerant here. */
  switch (gsettings_type[0])
    {
      case 'b': case 'y': case 'n': case 'q': case 'i': case 'u':
        if (registry_value.type == REG_DWORD)
          gsettings_value = g_variant_new (gsettings_type, registry_value.dword);
        break;

      case 't': case 'x':
        if (registry_value.type == REG_QWORD)
          {
            DWORDLONG qword_value = *(DWORDLONG *)registry_value.ptr;
            gsettings_value = g_variant_new (gsettings_type, qword_value);
          }
        break;

      default:
        if (registry_value.type == REG_SZ)
          {
            if (gsettings_type[0]=='s')
              gsettings_value = g_variant_new_string ((char *)registry_value.ptr);
            else
              {
                GError *error = NULL;
                gsettings_value = g_variant_parse (expected_type, registry_value.ptr, NULL, NULL, &error);

                if (error != NULL)
                g_message ("gregistrysettingsbackend: error parsing key %s: %s\n",
                           key_name, error->message);
              }
          }
          break;
    }

  g_free (gsettings_type);

  return gsettings_value;
}


typedef struct
{
  GRegistryBackend  *self;
  HKEY               hroot;
} RegistryWrite;

static gboolean
g_registry_backend_write_one (const char *key_name,
                              GVariant   *variant,
                              gpointer    user_data)
{
  GRegistryBackend *self;
  RegistryWrite    *action;
  RegistryValue     value;

  HKEY    hroot, hpath;
  gchar  *path_name, *value_name = NULL;
  DWORD   value_data_size;
  LPVOID  value_data;
  LONG    result;

  GNode    *node;
  gboolean  changed;

  action = user_data;
  self = G_REGISTRY_BACKEND (action->self);
  hroot = action->hroot;

  value.type = REG_NONE;
  value.ptr = NULL;

  const gchar *type_string = g_variant_get_type_string (variant);
  switch (type_string[0])
    {
      case 'b': case 'y': case 'n': case 'q': case 'i': case 'u':
        value.type = REG_DWORD;
        value.dword = g_variant_get_as_dword (variant);
        value_data_size = 4;
        value_data = &value.dword;
        break;

      case 'x': case 't':
        value.type = REG_QWORD;
        value.ptr = g_malloc (8);
        *(DWORDLONG *)value.ptr = g_variant_get_as_qword (variant);
        value_data_size = 8;
        value_data = value.ptr;
        break;

      default:
        value.type = REG_SZ;
        if (type_string[0]=='s')
          {
            gsize length;
            value.ptr = g_strdup (g_variant_get_string (variant, &length));
            value_data_size = length + 1;
            value_data = value.ptr;
          }
        else
          {
            GString *value_string;
            value_string = g_variant_print_string (variant, NULL, FALSE);
            value_data_size = value_string->len+1;
            value.ptr = value_data = g_string_free (value_string, FALSE);
          }
        break;
    }

  /* First update the cache, because the value may not have changed and we can
   * save a write.
   * 
   * If 'value' has changed then its memory will not be freed by update_node(),
   * because it will be stored in the node.
   */
  EnterCriticalSection (self->cache_lock);
  node = registry_cache_get_node_for_key (self->cache_root, key_name, TRUE);
  changed = registry_cache_update_node (node, value);
  LeaveCriticalSection (self->cache_lock);

  if (!changed)
    return FALSE;

  /* Block the next notification to any watch points above this location,
   * because they will each get triggered on a change that is already updated
   * in the cache.
   */
  registry_cache_block_notification (node);

  path_name = parse_key (key_name, NULL, &value_name);

  trace ("Set key: %s / %s\n", path_name, value_name);

  /* Store the value in the registry */
  result = RegCreateKeyExA (hroot, path_name, 0, NULL, 0, KEY_WRITE, NULL, &hpath, NULL);
  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "gregistrybackend: opening key %s failed", path_name+1);
      registry_value_free (value);
      g_free (path_name);
      return FALSE;
    }

  result = RegSetValueExA (hpath, value_name, 0, value.type, value_data, value_data_size);
  if (result != ERROR_SUCCESS)
      g_message_win32_error (result, "gregistrybackend: setting value %s\%s\\%s failed.\n",
                             self->base_path, path_name, value_name);

  /* If the write fails then it will seem like the value has changed until the
   * next execution (because we wrote to the cache first). There's no reason
   * for it to fail unless something is weirdly broken, however.
   */

  RegCloseKey (hpath);
  g_free (path_name);

  return FALSE;
};

/* The dconf write policy is to do the write while making out it succeeded, 
 * and then backtrack if it didn't. The registry functions are synchronous so
 * we can't do that. */

static gboolean
g_registry_backend_write (GSettingsBackend *backend,
                          const gchar      *key_name,
                          GVariant         *value,
                          gpointer          origin_tag)
{
  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
  LONG result;
  HKEY hroot;

  result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0,
                            KEY_WRITE, NULL, &hroot, NULL);
  if (result != ERROR_SUCCESS) {
    trace ("Error opening/creating key %s.\n", self->base_path);
    return FALSE;
  }

  RegistryWrite action = { self, hroot };
  g_registry_backend_write_one (key_name, value, &action);
  g_settings_backend_changed (backend, key_name, origin_tag);

  RegCloseKey (hroot);

  return TRUE;
}

static gboolean
g_registry_backend_write_tree (GSettingsBackend *backend,
                               GTree            *values,
                               gpointer          origin_tag)
{
  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
  LONG result;
  HKEY hroot;

  result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0,
                            KEY_WRITE, NULL, &hroot, NULL);
  if (result != ERROR_SUCCESS) {
    trace ("Error opening/creating key %s.\n", self->base_path);
    return FALSE;
  }

  RegistryWrite action = { self, hroot };
  g_tree_foreach (values, (GTraverseFunc)g_registry_backend_write_one,
                  &action);

  g_settings_backend_changed_tree (backend, values, origin_tag);
  RegCloseKey (hroot);

  return TRUE;
}

static void
g_registry_backend_reset (GSettingsBackend *backend,
                          const gchar      *key_name,
                          gpointer          origin_tag)
{
  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
  gchar *path_name, *value_name = NULL;
  GNode *cache_node;
  LONG result;
  HKEY hpath;

  /* Remove from cache */
  EnterCriticalSection (self->cache_lock);
  cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE);
  if (cache_node)
    registry_cache_destroy_tree (cache_node, self->watch);
  LeaveCriticalSection (self->cache_lock);

  /* Remove from the registry */
  path_name = parse_key (key_name, self->base_path, &value_name);

  result = RegOpenKeyExA (HKEY_CURRENT_USER, path_name, 0, KEY_SET_VALUE, &hpath);
  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "Registry: resetting key '%s'", path_name);
      g_free (path_name);
      return;
    }

  result = RegDeleteValueA (hpath, value_name);
  RegCloseKey (hpath);

  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "Registry: resetting key '%s'", path_name);
      g_free (path_name);
      return;
    }

  g_free (path_name);


  g_settings_backend_changed (backend, key_name, origin_tag);
}

/* Not implemented and probably beyond the scope of this backend */
static gboolean
g_registry_backend_get_writable (GSettingsBackend *backend,
                                 const gchar      *key_name)
{
  return TRUE;
}

static GPermission *
g_registry_backend_get_permission (GSettingsBackend *backend,
                                   const gchar      *key_name)
{
  return g_simple_permission_new (TRUE);
}


/********************************************************************************
 * Spot-the-difference engine
 ********************************************************************************/

static void
_free_watch (WatchThreadState *self,
             gint              index,
             GNode            *cache_node);

static void
registry_cache_item_reset_touched (GNode    *node,
                                   gpointer  data)
{
  RegistryCacheItem *item = node->data;
  item->touched = FALSE;
}

/* Delete a node and any children, for when it has been deleted from the registry */
static void
registry_cache_destroy_tree (GNode            *node,
                             WatchThreadState *self)
{
  RegistryCacheItem *item = node->data;

  g_node_children_foreach (node, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)registry_cache_destroy_tree, self);

  if (item->subscription_count > 0)
    {
      /* There must be some watches active if this node is a watch point */
      g_warn_if_fail (self->cache_nodes->len > 1);

      /* This is a watch point that has been deleted. Let's free the watch! */
      gint i;
      for (i=1; i<self->cache_nodes->len; i++)
        if (g_ptr_array_index (self->cache_nodes, i) == node)
          break;
      if (i >= self->cache_nodes->len)
        g_warning ("watch thread: a watch point was deleted, but unable to "
                   "find '%s' in the list of %i watch nodes\n", item->name,
                   self->cache_nodes->len-1);
      else
        {
          _free_watch (self, i, node);
          g_atomic_int_inc (&self->watches_remaining);
        }
    }
  _free_cache_item (node->data);
  g_node_destroy (node);
}

static void
registry_cache_remove_deleted (GNode    *node,
                               gpointer  data)
{
  RegistryCacheItem *item = node->data;

  if (!item->touched)
    registry_cache_destroy_tree (node, data);
}

/* Update cache from registry, and optionally report on the changes.
 * 
 * This function is sometimes called from the watch thread, with no locking. It
 * does call g_registry_backend functions, but this is okay because they only
 * access self->base which is constant.
 *
 * When looking at this code bear in mind the terminology: in the registry, keys
 * are containers that contain values, and other keys. Keys have a 'default'
 * value which we always ignore.
 *
 * n_parent_watches: a counter used to set the reference count of any new nodes
 *                   that are created - they should have as many references as
 *                   there are notifications that are watching them.
 */
static void
registry_cache_update (GRegistryBackend *self,
                       HKEY              hpath,
                       const gchar      *prefix,
                       const gchar      *partial_key_name,
                       GNode            *cache_node,
                       int               n_watches, 
                       GPtrArray        *changes)
{
  gchar  buffer[MAX_KEY_NAME_LENGTH + 1];
  gchar *key_name;
  gint   i;
  LONG   result;

  RegistryCacheItem *item = cache_node->data;

  if (item->subscription_count > 0)
    n_watches ++;

  /* prefix is the level that all changes occur below; partial_key_name should
   * be NULL on the first call to this function */
  key_name = g_build_path ("/", prefix, partial_key_name, NULL);

  trace ("registry cache update: %s. Node %x has %i children\n", key_name,
         cache_node, g_node_n_children (cache_node));

  /* Start by zeroing 'touched' flag. When the registry traversal is done, any untouched nodes
   * must have been deleted from the registry.
   */
  g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
                           registry_cache_item_reset_touched, NULL);

  /* Recurse into each subpath at the current level, if any */
  i = 0;
  while (1)
    {
      DWORD buffer_size = MAX_KEY_NAME_LENGTH;
      HKEY  hsubpath;

      result = RegEnumKeyEx (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL);
      if (result != ERROR_SUCCESS)
        break;

      result = RegOpenKeyEx (hpath, buffer, 0, KEY_READ, &hsubpath);
      if (result == ERROR_SUCCESS)
        {
          GNode             *subkey_node;
          RegistryCacheItem *child_item;

          subkey_node = registry_cache_find_immediate_child (cache_node, buffer);
          if (subkey_node == NULL)
            {
              RegistryValue null_value = {REG_NONE, {0}};
              subkey_node = registry_cache_add_item (cache_node, buffer,
                                                     null_value, n_watches);
            }


          registry_cache_update (self, hsubpath, prefix, buffer, subkey_node,
                                 n_watches, changes);
          child_item = subkey_node->data;
          child_item->touched = TRUE;
        }
      RegCloseKey (hsubpath);
    }

  if (result != ERROR_NO_MORE_ITEMS)
    g_message_win32_error (result, "gregistrybackend: error enumerating subkeys for cache.");

  /* Enumerate each value at 'path' and check if it has changed */
  i = 0;
  while (1)
    {
      DWORD              buffer_size = MAX_KEY_NAME_LENGTH;
      GNode             *cache_child_node;
      RegistryCacheItem *child_item;
      RegistryValue      value;
      gboolean           changed = FALSE;

      result = RegEnumValue (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL);
      if (result != ERROR_SUCCESS)
        break;

      if (buffer[0]==0)
        /* This is the key's 'default' value, for which we have no use. */
        continue;

      cache_child_node = registry_cache_find_immediate_child (cache_node, buffer);

      if (!registry_read (hpath, key_name, buffer, &value))
        continue;

      trace ("\tgot value %s for %s, node %x\n", registry_value_dump (value), buffer, cache_child_node);

      if (cache_child_node == NULL)
        {
          /* This is a new value */
          cache_child_node = registry_cache_add_item (cache_node, buffer, value,
                                                      n_watches);
          changed = TRUE;
        }
      else
        {
         /* For efficiency, instead of converting every value back to a GVariant to
          * compare it, we compare them as registry values (integers, or string
          * representations of the variant). The spurious change notifications that may
          * result should not be a big issue.
          *
          * Note that 'value' is swallowed or freed.
          */
          changed = registry_cache_update_node (cache_child_node, value);
        }

      child_item = cache_child_node->data;
      child_item->touched = TRUE;
      if (changed == TRUE && changes != NULL)
        {
          gchar *item;
          if (partial_key_name == NULL)
            item = g_strdup (buffer);
          else
            item = g_build_path ("/", partial_key_name, buffer, NULL);
          g_ptr_array_add (changes, item);
        }
    }

  if (result != ERROR_NO_MORE_ITEMS)
    g_message_win32_error (result, "gregistrybackend: error enumerating values for cache");

  /* Any nodes now left untouched must have been deleted, remove them from cache */
  g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
                           registry_cache_remove_deleted, self->watch);

  trace ("registry cache update complete.\n");
  g_free (key_name);
};



/***********************************************************************************
 * Thread to watch for registry change events
 ***********************************************************************************/

/* Called by watch thread. Apply for notifications on a registry key and its subkeys. */
static DWORD
registry_watch_key (HKEY hpath, HANDLE event)
{
  return RegNotifyChangeKeyValue (hpath, TRUE,
                                  REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
                                  event, TRUE);
}


/* One of these is sent down the pipe when something happens in the registry. */
typedef struct
{
  GRegistryBackend *self;
  gchar *prefix;          /* prefix is a gsettings path, all items are subkeys of this. */
  GPtrArray *items;       /* each item is a subkey below prefix that has changed. */
} RegistryEvent;

/* This handler runs in the main thread to emit the changed signals */
static gboolean
watch_handler (RegistryEvent *event)
{
  gint i;
  trace ("Watch handler: got event in %s, items %i.\n", event->prefix, event->items->len);

  /* GSettings requires us to NULL-terminate the array. */
  g_ptr_array_add (event->items, NULL);
  g_settings_backend_keys_changed (G_SETTINGS_BACKEND (event->self), event->prefix,
                                   (gchar const **)event->items->pdata, NULL);

  for (i=0; i<event->items->len; i++)
    g_free (g_ptr_array_index (event->items, i));
  g_ptr_array_free (event->items, TRUE);

  g_free (event->prefix);
  g_object_unref (event->self);
  
  g_slice_free (RegistryEvent, event);
  return FALSE;
};


static void
_free_watch (WatchThreadState *self,
             gint              index,
             GNode            *cache_node)
{
  HKEY    hpath;
  HANDLE  cond;
  gchar  *prefix;

  g_return_if_fail (index > 0 && index < self->events->len);

  cond       = g_ptr_array_index (self->events,      index);
  hpath      = g_ptr_array_index (self->handles,     index);
  prefix     = g_ptr_array_index (self->prefixes,    index);

  trace ("Freeing watch %i [%s]\n", index, prefix);
 
  /* These can be NULL if the watch was already dead, this can happen when eg.
   * a key is deleted but GSettings is still subscribed to it - the watch is
   * kept alive so that the unsubscribe function works properly, but does not
   * do anything.
   */
  if (hpath != NULL)
    RegCloseKey (hpath);

  if (cache_node != NULL)
    {
      //registry_cache_dump (G_REGISTRY_BACKEND (self->owner)->cache_root, NULL);
      registry_cache_unref_tree (cache_node);
    }

  CloseHandle (cond);
  g_free (prefix);

  /* As long as we remove from each array at the same time, it doesn't matter that
   * their orders get messed up - they all get messed up the same.
   */
  g_ptr_array_remove_index_fast (self->handles,     index);
  g_ptr_array_remove_index_fast (self->events,      index);
  g_ptr_array_remove_index_fast (self->prefixes,    index);
  g_ptr_array_remove_index_fast (self->cache_nodes, index);
}

static void
watch_thread_handle_message (WatchThreadState *self)
{
  switch (self->message.type)
    {
      case WATCH_THREAD_NONE:
        trace ("watch thread: you woke me up for nothin', man!");
        break;

      case WATCH_THREAD_ADD_WATCH:
        {
          RegistryWatch *watch = &self->message.watch;
          LONG           result;
          result = registry_watch_key (watch->hpath, watch->event);
          if (result == ERROR_SUCCESS)
            {
              g_ptr_array_add (self->events,      watch->event);
              g_ptr_array_add (self->handles,     watch->hpath);
              g_ptr_array_add (self->prefixes,    watch->prefix);
              g_ptr_array_add (self->cache_nodes, watch->cache_node);
              trace ("watch thread: new watch on %s, %i total\n", watch->prefix,
                     self->events->len);
            }
          else
            {
              g_message_win32_error (result, "watch thread: could not watch %s", watch->prefix);
              CloseHandle (watch->event);
              RegCloseKey (watch->hpath);
              g_free (watch->prefix);
              registry_cache_unref_tree (watch->cache_node);
            }
          break;
        }

      case WATCH_THREAD_REMOVE_WATCH:
        {
          GNode             *cache_node;
          RegistryCacheItem *cache_item;
          gint               i;

          for (i=1; i<self->prefixes->len; i++)
            if (strcmp (g_ptr_array_index (self->prefixes, i),
                        self->message.watch.prefix) == 0)
                break;
 
          if (i >= self->prefixes->len)
            {
              /* Don't make a fuss if the prefix is not being watched because
               * maybe the path was deleted so we removed the watch.
               */
              trace ("unsubscribe: prefix %s is not being watched [%i things are]!\n",
                     self->message.watch.prefix, self->prefixes->len);
              g_free (self->message.watch.prefix);
              break;
            }

          cache_node = g_ptr_array_index (self->cache_nodes, i);

          trace ("watch thread: unsubscribe: freeing node %x, prefix %s, index %i\n",
                 (guint)cache_node, self->message.watch.prefix, i);
          if (cache_node != NULL)
            {
              cache_item = cache_node->data;

              /* There may be more than one GSettings object subscribed to this
               * path, only free the watch when the last one unsubscribes.
               */
              cache_item->subscription_count --;
              if (cache_item->subscription_count > 0)
                break;
            }

          _free_watch (self, i, cache_node);
          g_free (self->message.watch.prefix);

          g_atomic_int_inc (&self->watches_remaining);
          break;
        }

      case WATCH_THREAD_STOP:
        {
          gint i;

          /* Free any remaining cache and watch handles */
          for (i=1; i<self->events->len; i++)
            _free_watch (self, i, g_ptr_array_index (self->cache_nodes, i));

          SetEvent (self->message_received_event);
          ExitThread (0);
        }
    }

  self->message.type = WATCH_THREAD_NONE;
  SetEvent (self->message_received_event);
}


/* Thread which watches for win32 registry events */
static DWORD WINAPI
watch_thread_function (LPVOID parameter)
{
  WatchThreadState *self = (WatchThreadState *)parameter;
  DWORD result;

  self->events = g_ptr_array_new ();
  self->handles = g_ptr_array_new ();
  self->prefixes = g_ptr_array_new ();
  self->cache_nodes = g_ptr_array_new ();
  g_ptr_array_add (self->events, self->message_sent_event);
  g_ptr_array_add (self->handles, NULL);
  g_ptr_array_add (self->prefixes, NULL);
  g_ptr_array_add (self->cache_nodes, NULL);

  while (1)
    {
      trace ("watch thread: going to sleep; %i events watched.\n", self->events->len);
      result = WaitForMultipleObjects (self->events->len, self->events->pdata, FALSE, INFINITE);

      if (result == WAIT_OBJECT_0)
        {
          /* A message to you. The sender (main thread) will block until we signal the received
           * event, so there should be no danger of it sending another before we receive the
           * first.
           */
          watch_thread_handle_message (self);
        }
      else if (result > WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + self->events->len)
        {
          HKEY               hpath;
          HANDLE             cond;
          gchar             *prefix;
          GNode             *cache_node;
          RegistryCacheItem *cache_item;
          RegistryEvent     *event;

          /* One of our notifications has triggered. All we know is which one, and which key
           * this is for. We do most of the processing here, because we may as well. If the
           * registry changes further while we are processing it doesn't matter - we will then
           * receive another change notification from the OS anyway.
           */
          gint notify_index = result - WAIT_OBJECT_0;
          hpath      = g_ptr_array_index (self->handles,     notify_index);
          cond       = g_ptr_array_index (self->events,      notify_index);
          prefix     = g_ptr_array_index (self->prefixes,    notify_index);
          cache_node = g_ptr_array_index (self->cache_nodes, notify_index);

          trace ("Watch thread: notify received on prefix %i: %s.\n", notify_index, prefix);

          if (cache_node == NULL)
            {
              /* This path has been deleted */
              trace ("Notify received on a path that was deleted :(\n");
              continue;
            }

          /* Firstly we need to reapply for the notification, because (what a
           * sensible API) we won't receive any more. MSDN is pretty
           * inconsistent on this matter:
           *   http://msdn.microsoft.com/en-us/library/ms724892%28VS.85%29.aspx
           *   http://support.microsoft.com/kb/236570
           * But my tests (on Windows XP SP3) show that we need to reapply
           * each time.
           */
          result = registry_watch_key (hpath, cond);

          if (result != ERROR_SUCCESS)
            {
              /* Watch failed, most likely because the key has just been
               * deleted. Free the watch and unref the cache nodes.
               */
             if (result != ERROR_KEY_DELETED)
               g_message_win32_error (result, "watch thread: failed to watch %s", prefix);
             _free_watch (self, notify_index, cache_node);
             g_atomic_int_inc (&self->watches_remaining);
             continue;
            }

          /* The notification may have been blocked because we just changed
           * some data ourselves.
           */
          cache_item = cache_node->data;
          if (cache_item->block_count)
            {
              cache_item->block_count --;
              trace ("Watch thread: notify blocked at %s\n", prefix);
              continue;
            }
  
          /* Now we update our stored cache from registry data, and find which keys have
           * actually changed. If more changes happen while we are processing, we will get
           * another event because we have reapplied for change notifications already.
           *
           * Working here rather than in the main thread is preferable because the UI is less
           * likely to block (only when changing notification subscriptions).
           */
          event = g_slice_new (RegistryEvent);

          event->self = G_REGISTRY_BACKEND (self->owner);
          g_object_ref (self->owner);

          event->items = g_ptr_array_new ();

          EnterCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock);
          registry_cache_update (G_REGISTRY_BACKEND (self->owner), hpath,
                                 prefix, NULL, cache_node, 0, event->items);
          LeaveCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock);

          if (event->items->len > 0)
            {
              event->prefix = g_strdup (prefix);
              g_idle_add ((GSourceFunc) watch_handler, event);
            }
          else
            {
              g_ptr_array_free (event->items, TRUE);
              g_slice_free (RegistryEvent, event);
            }
        }
      else
        {
          /* God knows what has happened */
          g_message_win32_error (GetLastError(), "watch thread: WaitForMultipleObjects error");
        }
    }

  return -1;
}

static gboolean
watch_start (GRegistryBackend *self)
{
  WatchThreadState *watch;

  g_return_val_if_fail (self->watch == NULL, FALSE);

  self->cache_lock = g_slice_new (CRITICAL_SECTION);
  InitializeCriticalSection (self->cache_lock);

  watch = g_slice_new (WatchThreadState);
  watch->owner = G_SETTINGS_BACKEND (self);

  watch->watches_remaining = MAX_WATCHES;

  watch->message_lock = g_slice_new (CRITICAL_SECTION);
  InitializeCriticalSection (watch->message_lock);
  watch->message_sent_event = CreateEvent (NULL, FALSE, FALSE, NULL);
  watch->message_received_event = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (watch->message_sent_event == NULL || watch->message_received_event == NULL)
    {
      g_message_win32_error (0, "gregistrybackend: Failed to create sync objects.");
      goto fail_1;
    }

  /* Use a small stack to make the thread more lightweight. */
  watch->thread = CreateThread (NULL, 1024, watch_thread_function, watch, 0, NULL);
  if (watch->thread == NULL)
    {
      g_message_win32_error (0, "gregistrybackend: Failed to create notify watch thread.");
      goto fail_2;
    }

  self->watch = watch;

  return TRUE;

fail_2:
  DeleteCriticalSection (self->cache_lock);
  g_slice_free (CRITICAL_SECTION, self->cache_lock);
  DeleteCriticalSection (watch->message_lock);
  g_slice_free (CRITICAL_SECTION, watch->message_lock);
  CloseHandle (watch->message_sent_event);
  CloseHandle (watch->message_received_event);
fail_1:
  g_slice_free (WatchThreadState, watch);
  return FALSE;
}

/* This function assumes you hold the message lock! */
static void
watch_stop_unlocked (GRegistryBackend *self)
{
  WatchThreadState *watch = self->watch;
  DWORD result;
  g_return_if_fail (watch != NULL);

  watch->message.type = WATCH_THREAD_STOP;
  SetEvent (watch->message_sent_event);

  /* This is signalled as soon as the message is received. We must not return
   * while the watch thread is still firing off callbacks. Freeing all of the
   * memory is done in the watch thread after this is signalled.
   */
  result = WaitForSingleObject (watch->message_received_event, INFINITE);
  if (result != WAIT_OBJECT_0)
    {
      g_warning ("gregistrybackend: unable to stop watch thread.");
      return;
    }

  LeaveCriticalSection (watch->message_lock);
  DeleteCriticalSection (watch->message_lock);
  DeleteCriticalSection (self->cache_lock);
  g_slice_free (CRITICAL_SECTION, watch->message_lock);
  g_slice_free (CRITICAL_SECTION, self->cache_lock);
  CloseHandle (watch->message_sent_event);
  CloseHandle (watch->message_received_event);
  CloseHandle (watch->thread);
  g_slice_free (WatchThreadState, watch);

  trace ("\nwatch thread: %x: all data freed.\n", self);
  self->watch = NULL;
};

static gboolean
watch_add_notify (GRegistryBackend *self,
                  HANDLE            event,
                  HKEY              hpath,
                  gchar            *gsettings_prefix)
{
  WatchThreadState  *watch = self->watch;
  GNode             *cache_node;
  RegistryCacheItem *cache_item;
  DWORD              result;

  g_return_val_if_fail (watch != NULL, FALSE);
  trace ("watch_add_notify: prefix %s.\n", gsettings_prefix);

  /* Duplicate tree into the cache in the main thread, before we add the notify: if we do it in the
   * thread we can miss changes while we are caching.
   */
  EnterCriticalSection (self->cache_lock);
  cache_node = registry_cache_get_node_for_key (self->cache_root, gsettings_prefix, TRUE);

  g_return_val_if_fail (cache_node != NULL, FALSE);
  g_return_val_if_fail (cache_node->data != NULL, FALSE);
  
  cache_item = cache_node->data;

  cache_item->subscription_count ++;
  if (cache_item->subscription_count > 1)
    {
      trace ("watch_add_notify: prefix %s already watched, %i subscribers.\n",
             gsettings_prefix, cache_item->subscription_count);
      return FALSE;
    }

  registry_cache_ref_tree (cache_node);
  registry_cache_update (self, hpath, gsettings_prefix, NULL, cache_node, 0, NULL);
  //registry_cache_dump (self->cache_root, NULL);
  LeaveCriticalSection (self->cache_lock);

  EnterCriticalSection (watch->message_lock);
  watch->message.type = WATCH_THREAD_ADD_WATCH;
  watch->message.watch.event      = event;
  watch->message.watch.hpath      = hpath;
  watch->message.watch.prefix     = gsettings_prefix;
  watch->message.watch.cache_node = cache_node;

  SetEvent (watch->message_sent_event);

  /* Wait for the received event in return, to avoid sending another message before the first
   * one was received. If it takes > 200ms there is a possible race but the worst outcome is
   * a notification is ignored.
   */
  result = WaitForSingleObject (watch->message_received_event, 200);
  #ifdef TRACE
    if (result != WAIT_OBJECT_0)
      trace ("watch thread is slow to respond - notification may not be added.");
  #endif
  LeaveCriticalSection (watch->message_lock);

  return TRUE;
};


static void
watch_remove_notify (GRegistryBackend *self,
                     const gchar      *key_name)
{
  WatchThreadState *watch = self->watch;
  LONG     result;

  if (self->watch == NULL)
    /* Here we assume that the unsubscribe message is for somewhere that was
     * deleted, and so it has already been removed and the watch thread has
     * stopped.
     */
    return;

  EnterCriticalSection (watch->message_lock);
  watch->message.type = WATCH_THREAD_REMOVE_WATCH;
  watch->message.watch.prefix = g_strdup (key_name);

  SetEvent (watch->message_sent_event);

  /* Wait for the received event in return, to avoid sending another message before the first
   * one was received.
   */
  result = WaitForSingleObject (watch->message_received_event, INFINITE);

  if (result != ERROR_SUCCESS)
    g_warning ("unsubscribe from %s: message not acknowledged\n", key_name);

  if (g_atomic_int_get (&watch->watches_remaining) >= MAX_WATCHES)
    /* Stop it before any new ones can get added and confuse things */
    watch_stop_unlocked (self); 
  else
    LeaveCriticalSection (watch->message_lock);
}

/* dconf semantics are: if the key ends in /, watch the keys underneath it - if not, watch that
 * key. Our job is easier because keys and values are separate.
 */
static void
g_registry_backend_subscribe (GSettingsBackend *backend,
                              const char       *key_name)
{
  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
  gchar *path_name, *value_name = NULL;
  HKEY hpath;
  HANDLE event;
  LONG result;

  if (self->watch == NULL)
    if (!watch_start (self))
      return;

  if (g_atomic_int_dec_and_test (&self->watch->watches_remaining))
    {
      g_atomic_int_inc (&self->watch->watches_remaining);
      g_warning ("subscribe() failed: only %i different paths may be watched.\n", MAX_WATCHES);
      return;
    }

  path_name = parse_key (key_name, self->base_path, &value_name);

  /* Must check for this, otherwise strange crashes occur because the cache
   * node that is being watched gets freed. All path names to subscribe must
   * end in a slash!
   */
  if (value_name != NULL && *value_name != 0)
    g_warning ("subscribe() failed: path must end in a /, got %s\n", key_name);

  trace ("Subscribing to %s [registry %s / %s] - watch %x\n", key_name, path_name, value_name, self->watch);


  /* Give the caller the benefit of the doubt if the key doesn't exist and create it. The caller
   * is almost certainly a new g_settings with this path as base path. */
  result = RegCreateKeyExA (HKEY_CURRENT_USER, path_name, 0, NULL, 0, KEY_READ, NULL, &hpath,
                            NULL);
  g_free (path_name);

  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "gregistrybackend: Unable to subscribe to key %s.", key_name);
      g_atomic_int_inc (&self->watch->watches_remaining);
      return;
    }

  event = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (event == NULL)
    {
      g_message_win32_error (result, "gregistrybackend: CreateEvent failed.\n");
      g_atomic_int_inc (&self->watch->watches_remaining);
      RegCloseKey (hpath);
      return;
    }

  /* The actual watch is added by the thread, which has to re-subscribe each time it
   * receives a change. */
  if (!watch_add_notify (self, event, hpath, g_strdup (key_name)))
    g_atomic_int_inc (&self->watch->watches_remaining);
}

static void
g_registry_backend_unsubscribe (GSettingsBackend *backend,
                                const char       *key_name)
{
  trace ("unsubscribe: %s.\n", key_name);

  watch_remove_notify (G_REGISTRY_BACKEND (backend), key_name);
}


/********************************************************************************
 * Object management junk
 ********************************************************************************/

GSettingsBackend *
g_registry_backend_new (void) {
  return g_object_new (G_TYPE_REGISTRY_BACKEND, NULL);
}

static void
g_registry_backend_finalize (GObject *object)
{
  GRegistryBackend  *self = G_REGISTRY_BACKEND (object);
  RegistryCacheItem *item;

  item = self->cache_root->data;
  g_warn_if_fail (item->ref_count == 1);

  _free_cache_item (item);
  g_node_destroy (self->cache_root);

  if (self->watch != NULL)
    {
      EnterCriticalSection (self->watch->message_lock);
      watch_stop_unlocked (self);
    }

  g_free (self->base_path);
}

static void
g_registry_backend_class_init (GRegistryBackendClass *class)
{
  GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->finalize = g_registry_backend_finalize;

  backend_class->read = g_registry_backend_read;
  backend_class->write = g_registry_backend_write;
  backend_class->write_tree = g_registry_backend_write_tree;
  backend_class->reset = g_registry_backend_reset;
  backend_class->get_writable = g_registry_backend_get_writable;
  backend_class->get_permission = g_registry_backend_get_permission;
  backend_class->subscribe = g_registry_backend_subscribe;
  backend_class->unsubscribe = g_registry_backend_unsubscribe;
}

static void
g_registry_backend_init (GRegistryBackend *self)
{
  self->base_path = g_strdup_printf ("Software\\GSettings");

  RegistryCacheItem *item = g_slice_new (RegistryCacheItem);
  item->value.type = REG_NONE;
  item->value.ptr = NULL;
  item->name = g_strdup ("<root>");
  item->ref_count = 1;
  self->cache_root = g_node_new (item);

  self->watch = NULL;
}
